/* Datatype.js
 *
 * Copyright 2006, Tim Dwelle.
 *
 * Licensed under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of
 * the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in
 * writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.  See
 * the License for the specific language governing
 * permissions and limitations under the License.
 *
 */

$package("dowry.datatype");

/**
 * A baseclass for implementations of datatype.
 *
 */
$class("Datatype").$as(
{
    /**
     * Constant for the error message when validation finds
     * a null value when a value is required.
     *
     */
    ERROR_REQUIRED : Dowry.msgs.getMessage("ERROR_REQUIRED"),

    /**
     * Constant indicating that additional validation needs
     * to be performed on the remote server.  This may be
     * thrown from the validate() method.
     *
     */
    REMOTE_VALIDATE : Dowry.REMOTE_VALIDATE,

    /**
     * Property indicating the name of the entity to which
     * this datatype is bound.
     *
     */
    entity : null,

    /**
     * Property indicating the name of the remote Java
     * class to which this type refers.
     *
     */
    javaClass : null,

    /**
     * Property indicating the name of the property on the
     * specified entity to which this datatype is bound.
     *
     */
    property : null,

    /**
     * Boolean property indicating whether or not the value
     * can be null.  The default is false (not required).
     *
     */
    required : false,

    /**
     * Property representing the name of the Datatype's
     * specific type.  This type is a string, and not
     * necessarily equivalent to the object's class.
     *
     */
    type : null,

    /**
     * Compares two values for order, based on the
     * comparison rules for this datatype. Returns a
     * negative integer, zero, or a positive integer
     * indicating that the first value is less than, equal
     * to, or greater than the second.
     *
     * @param value1  the first value to be compared
     *
     * @param value2  the second value to be compared
     *
     * @return        a negative integer, zero, or a
     *                positive integer indicating that
     *                the first value is less than, equal
     *                to, or greater than the second.
     *
     */
    compare : function(value1, value2)
    {
        var rc = -1;

        if (value1 == value2)
        {
            rc = 0;
        }
        else if (value1 == null)
        {
            rc = -1;
        }
        else if (value2 == null)
        {
            rc = 1;
        }
        else if (value1 > value2)
        {
            rc = 1;
        }

        return rc;
    },

    /**
     * This method bundles up the relevant information about
     * this datatype (for instance, so it can be validated
     * on the server.) It returns this configuration as a
     * map.
     *
     * <p>
     * This default implementation will include any
     * properties of this datatype that are 1) not prefaced
     * with an underscore, 2) not in all capital letters,
     * and 3) are a string, boolean, or number type.
     * </p>
     *
     * @param  id    the identifier for the field associated
     *               with this datatype (optional)
     *
     * @param  val   the field value associated with this
     *               datatype (optional)
     *
     * @return       an object containing the datatype
     *               information as a map
     *
     */
    toConfiguration : function(id, val)
    {
        var cfg = new Object();

        for (var key in this)
        {
            var val = this[key];

            // skip keys that are prefaced with
            // underscores (these are private)
            if (key.charAt(0) == "_")
            {
                continue;
            }

            // skip keys that are in all capital
            // letters (these are constants)
            if (key == key.toUpperCase())
            {
                continue;
            }

            // make sure we have a value
            // and that it represents a
            // supported datatype
            var valType = typeof val;
            if (valType != "string" &&
                valType != "number" &&
                valType != "boolean")
            {
                continue;
            }

            // still here... it is a
            // good key-value pair
            cfg[key] = val;
        }

        // add the id, if specified
        if (id != null)
        {
            cfg.id = id;
        }

        // add the value, if specified...
        // this may need to be overridden
        // in subclasses, if there are more
        // involved rules for how the value
        // should be serialized
        if (val != null)
        {
            cfg.value = val;
        }

        return cfg;
    },

    /**
     * Converts some raw value to a value of the appropriate
     * datatype.
     *
     * @param val  the original, raw value representation
     *
     * @return     the value converted to the appropriate
     *             datatype
     *
     */
    toDatatype : function(val)
    {
        return this.trim(val);
    },

    /**
     * Converts some value of the appropriate datatype to
     * a string representation.
     *
     * @param val  the original value of the appropriate
     *             datatype
     *
     * @return     the value converted to a string
     *
     */
    toFormattedString : function(val)
    {
        var s = null;

        if (val != null)
        {
            s = String(val);
            s = this.trim(s);
        }

        return s;
    },

    /**
     * Trims whitespace from a string. If the provided
     * value is not a string, then the object is returned
     * unchanged.  If the result of trimming is an empty
     * string, null is returned.
     *
     * @param  s   the string to be trimmed
     *
     * @return     new string with its whitespace trimmed
     *
     */
    trim : function(s)
    {
        if (typeof s == "string")
        {
            s = s.replace(/^\s*|\s*$/g,"");
            if (s == "")
            {
                s = null;
            }
        }

        return s;
    },

    /**
     * Validates a value representation according to the
     * validation rules for the datatype.
     *
     * <p>
     * In some cases, the validation will need to be
     * peformed on the server-side.  In those cases,
     * override the validate() method to contain the
     * line:
     * </p>
     *
     * <pre>
     *     throw this.REMOTE_VALIDATE;
     * </pre>
     *
     * <p>
     * This can be performed anywhere in the method...
     * so for instance, you can do all possible client-side
     * validation before, flagging this to be further
     * validated on the server.  However, code calling
     * <code>validate()</code> directly should be aware that
     * this "exception" may be thrown.
     * </p>
     *
     * <p>
     * This base implementation of valide simply
     * validates that the provided value is not null, if the
     * datatype is required.
     * </p>
     *
     * @param val    a value representation to be validated
     *
     * @return       the string validation error message,
     *               if the validation fails.
     *
     * @throws       REMOTE_VALIDATE, if additional
     *               validation needs to be performed on the
     *               remote server
     *
     */
    validate : function(val)
    {
        var msg = null;

        if (val == null && this.required)
        {
            msg = this.ERROR_REQUIRED;
        }

        return msg;
    }
});